package com.yb.loadbalancer.config;

import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Copyright (C), 2022-2023, 姚兵
 * Author: 32210
 * Date: 2023/5/26 12:22
 * FileName: MyLoadBalancer
 * Description:
 */
@Slf4j
public class SmoothRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final String serviceId;
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;


    private Random random = new Random();
    @Getter
    @Setter
    @ToString
    public static class Instance{
        private String ip;

        private int port;
        private double weight;
        private double curWeight;

        private boolean healthy;

        public boolean equals(final Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof Instance)) {
                return false;
            } else {
                Instance other = (Instance)o;
                if (!other.canEqual(this)) {
                    return false;
                } else if (this.getPort() != other.getPort()) {
                    return false;
                }  else if (this.isHealthy() != other.isHealthy()) {
                    return false;
                } else {
                    Object this$ip = this.getIp();
                    Object other$ip = other.getIp();
                    if (this$ip == null) {
                        if (other$ip != null) {
                            return false;
                        }
                    } else if (!this$ip.equals(other$ip)) {
                        return false;
                    }

                    return true;
                }
            }
        }

        List<Instance> orderedInstance=new ArrayList<>();
        protected boolean canEqual(final Object other) {
            return other instanceof Instance;
        }

        public int hashCode() {
            int result = 1;
            result = result * 59 + this.getPort();
            result = result * 59 + (this.isHealthy() ? 79 : 97);
            Object $ip = this.getIp();
            result = result * 59 + ($ip == null ? 43 : $ip.hashCode());
            return result;
        }
    }



    final Map<Instance,Double> curWeightMap = new ConcurrentHashMap<>();
    public SmoothRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                   String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);

        return supplier.get(request).next().map(this::getInstanceResponse);
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }

        // see
        // com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery.hostToServiceInstance()
        Map<Instance, ServiceInstance> instanceMap = new HashMap<>();

        List<Instance> instanceList = instances.stream().map(serviceInstance -> {
            Instance instance = new Instance();
            Map<String, String> metadata = serviceInstance.getMetadata();
            instance.setIp(serviceInstance.getHost());
            instance.setPort(serviceInstance.getPort());
            instance.setWeight(Double.parseDouble(metadata.get("nacos.weight")));
            instance.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy")));
            Double curWeight = curWeightMap.get(instance);
            instance.curWeight=curWeight!=null?curWeight:0;
            instanceMap.put(instance, serviceInstance);
            return instance;
        }).collect(Collectors.toList());


        //获取总的权重
        double totalWeight = totalWeight(instanceList);

        //修改当前权重
        instanceList.stream().forEach(item->{
            item.curWeight+=item.weight;
            curWeightMap.put(item,item.curWeight);
        });

        Optional<Instance> reduce = instanceList.stream().reduce((x, y) -> x.curWeight >= y.curWeight ? x : y);
        Instance instance = reduce.get();
        //修改当前的权重
        instance.curWeight-=totalWeight;
        curWeightMap.put(instance,instance.curWeight);
        log.info("{}",instance);

        ServiceInstance serviceInstance = instanceMap.get(instance);

        return new DefaultResponse(serviceInstance);
    }


    private double totalWeight(List<Instance> instanceList){
        Optional<Double> reduce = instanceList.stream().map(Instance::getWeight).reduce(Double::sum);
        return reduce.get();
    }

    @Override
    public Mono<Response<ServiceInstance>> choose() {
        return ReactorServiceInstanceLoadBalancer.super.choose();
    }
}
